/*
 * Copyright (c) 2008, Xiong Yingfei, the University of Tokyo All rights
 * reserved.
 */
package ac.jp.u_tokyo.SyncLib.graphCombinator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import ac.jp.u_tokyo.SyncLib.ModTypeUnsupportedException;
import ac.jp.u_tokyo.SyncLib.StaticCombinatorBase;
import ac.jp.u_tokyo.SyncLib.Mod;
import ac.jp.u_tokyo.SyncLib.NullMod;
import ac.jp.u_tokyo.SyncLib.StateSync;
import ac.jp.u_tokyo.SyncLib.Sync;
import ac.jp.u_tokyo.SyncLib.SynchronizationFailedException;
import ac.jp.u_tokyo.SyncLib.graphCombinator.Variable.SyncIndexPair;
import ac.jp.u_tokyo.SyncLib.util.Helper;

public class GraphCombinator extends StaticCombinatorBase
{
	Variable[] _parameters;

	//	Object[] _initialValues;

	SyncHolder[] _allHolders;

	public GraphCombinator(Variable[] parameters, SyncHolder[] holders/*,
					Object[] initialValues*/)
	{
		super(new Object[] {}, toSync(holders));
		_parameters = parameters;
		//		_initialValues = initialValues;
		_allHolders = holders;
		assert isWellFormedGraph(parameters, holders);
		for (int i = 0; i < holders.length; i++)
		{
			holders[i].setIndex(i);
		}
	}

	@Override
	public boolean isModConcerned(int index, Mod mod)
			throws ModTypeUnsupportedException
	{
		for (SyncIndexPair pair : _parameters[index])
		{
			if (pair.getSyncHolder().getSync().isModConcerned(pair.getIndex(),
					mod)) return true;
		}
		return false;
	}

	private boolean isWellFormedGraph(Variable[] parameters,
			SyncHolder[] holders/*, Object[] paraValues*/)
	{

		Set<SyncHolder> unvisitedHolders = new HashSet<SyncHolder>();
		Set<SyncHolder> visitedHolders = new HashSet<SyncHolder>();

		for (int i = 0; i < parameters.length; i++)
		{
			for (Variable.SyncIndexPair pair : parameters[i])
			{
				unvisitedHolders.add(pair.getSyncHolder());
			}
		}

		while (unvisitedHolders.size() > 0)
		{
			SyncHolder curSH = unvisitedHolders.iterator().next();
			Set<Variable> vars = new HashSet<Variable>();
			for (int i = 0; i < curSH.getVariableCount(); i++)
			{
				Variable variable = curSH.getVariable(i);
				if (variable == null)
				{
					System.err
							.println("the synchronizer is not fully connected! index:"
									+ i + " synchronizer: " + curSH.getSync());
					return false;
				}

				vars.add(variable);
			}
			for (Variable v : vars)
				for (Variable.SyncIndexPair pair : v)
					if (!visitedHolders.contains(pair.getSyncHolder()))
						unvisitedHolders.add(pair.getSyncHolder());

			unvisitedHolders.remove(curSH);
			visitedHolders.add(curSH);

		}

		if (!new HashSet<SyncHolder>(Arrays.asList(holders))
				.equals(visitedHolders))
		{
			System.err.println("some synchronizers not connected!");
			return false;
		}
		return true;
	}

	private static Sync[] toSync(SyncHolder[] holders)
	{
		Sync[] sync = new Sync[holders.length];
		for (int i = 0; i < sync.length; i++)
		{
			assert holders[i] != null : "the sync holder is null! index:" + i;
			sync[i] = holders[i].getSync();
		}
		return sync;
	}

	public int getParaCount()
	{
		return _parameters.length;
	}

	public Mod[] doResynchronize(Object[] values, Mod[] modifications)
			throws SynchronizationFailedException
	{
		Set<SyncHolder> affectedSyncs = new HashSet<SyncHolder>();
//		Set<SyncHolder> noModSyncs = new HashSet<SyncHolder>();
		Collection<Variable> modifiedVariables = new HashSet<Variable>();

		affectedSyncs.addAll(Arrays.asList(_allHolders));
//		noModSyncs.addAll(Arrays.asList(_allHolders));

		for (int i = 0; i < values.length; i++)
		{
			Variable v = _parameters[i];
			v.setValue(values[i]);

			Mod mod = modifications[i];
			assert mod != null : i;
			if (false == mod instanceof NullMod)
			{
				modifiedVariables.add(v);
				v.setMod(mod);
				v.setHighMod(mod);
//				for (Variable.SyncIndexPair pair : v)
//				{
//					affectedSyncs.add(pair.getSyncHolder());
//					noModSyncs.remove(pair.getSyncHolder());
//				}
			}
		}

		try
		{
			Mod[] result = propagate(affectedSyncs, /*noModSyncs,*/
					modifiedVariables, new SyncInvoker() {

						public Mod[] synchronize(SyncHolder sh,
								Collection<Variable> modifiedVariables)
								throws SynchronizationFailedException
						{
							Mod[] result;
							Object[] valueOnVars = sh
									.getValueListFromVars();
							try
							{
								result = invokeResync(sh.getIndex(), valueOnVars, sh
										.getModListFromVars());
							}
							catch (SynchronizationFailedException e)
							{
								result = sh.getModListFromVars();
							}
							
							for (int i = 0; i < valueOnVars.length; i++) {
								valueOnVars[i] = result[i].apply(valueOnVars[i]);
							}
							
							Mod[] highResult = invokeResync(sh.getIndex(), valueOnVars, sh
									.getHighModListFromVars());
							
							storeHighModsToVariables(modifiedVariables, sh,
									highResult);
							for (int i = 0; i < result.length; i++)
							{
								result[i] = result[i].merge(highResult[i]);
							}

							return result;
						}

					});
			return result;
		}
		finally
		{
			for (int i = 0; i < _parameters.length; i++)
			{
				_parameters[i].setValue(null);
			}
		}

	}

	public Mod[] doSynchronize(Mod[] modifications)
			throws SynchronizationFailedException
	{
		Collection<Variable> modifiedVariables = new HashSet<Variable>();
		assert getParaCount() == modifications.length : "paraSize: "
				+ getParaCount() + " modSize: " + modifications.length;
		for (int i = 0; i < getParaCount(); i++)
		{
			assert modifications[i] != null;
			if (!NullMod.INSTANCE.equals(modifications[i]))
			{
				modifiedVariables.add(_parameters[i]);
				_parameters[i].setMod(modifications[i]);
			}
		}

		//		System.out.println("The modifiedvars: " + modifiedVariables.size());

		Set<SyncHolder> affectedSyncs = new HashSet<SyncHolder>();
		for (Variable v : modifiedVariables)
		{
			for (Variable.SyncIndexPair pair : v)
			{
				affectedSyncs.add(pair.getSyncHolder());
			}
		}

		return propagate(affectedSyncs, /*new HashSet<SyncHolder>(),*/
				modifiedVariables, new SyncInvoker() {

					public Mod[] synchronize(SyncHolder sh,
							Collection<Variable> modifiedVariables)
							throws SynchronizationFailedException
					{
						return invokeSync(sh.getIndex(), sh
								.getModListFromVars());
					}

				});

	}

	private interface SyncInvoker
	{
		Mod[] synchronize(SyncHolder sh, Collection<Variable> modifiedVariables)
				throws SynchronizationFailedException;
	}

	private Mod[] propagate(Set<SyncHolder> initialSyncs,
			//Set<SyncHolder> initialNoModSyncs,
			Collection<Variable> modifiedVariables, SyncInvoker invoker)
			throws SynchronizationFailedException
	{
		SortedSet<SyncHolder> priorityQueue = new TreeSet<SyncHolder>();
		priorityQueue.addAll(initialSyncs);

//		SortedSet<SyncHolder> noModPriorityQueue = new TreeSet<SyncHolder>();
		//noModPriorityQueue.addAll(initialNoModSyncs);

		try
		{
			while (priorityQueue.size() > 0 )//|| noModPriorityQueue.size() > 0)
			{

				SyncHolder curSH;
//				if (priorityQueue.size() > 0)
					curSH = priorityQueue.last();
//				else
//					curSH = noModPriorityQueue.last();

				Mod[] result = invoker.synchronize(curSH, modifiedVariables);

				//				affectedSyncs.clear();
				//				for (Variable v : affectedVars)
				//				{
				//					for (Variable.SyncIndexPair pair : v)
				//						if (pair.getSyncHolder() != curSH)
				//						{
				//							affectedSyncs.add(pair.getSyncHolder());
				//							noModPriorityQueue.remove(pair.getSyncHolder());
				//						}
				//				}

				Collection<SyncHolder> affectedSyncs = findChangedSyncs(curSH,
						result);
				priorityQueue.addAll(affectedSyncs);
//				noModPriorityQueue.removeAll(affectedSyncs);
				priorityQueue.remove(curSH);
//				noModPriorityQueue.remove(curSH);

				storeModsToVariables(modifiedVariables, curSH, result);

			}

			return prepareResult();
		}
		finally
		{
			clearModifications(modifiedVariables);

		}
	}

	private void clearModifications(Collection<Variable> modifiedVariables)
	{
		for (Variable v : modifiedVariables)
		{
			v.setMod(NullMod.INSTANCE);
			v.setHighMod(NullMod.INSTANCE);
		}
	}

	private Mod[] prepareResult()
	{
		Mod[] result = new Mod[_parameters.length];
		for (int i = 0; i < _parameters.length; i++)
		{
			result[i] = _parameters[i].getMod();
		}
		return result;
	}

	private Collection<SyncHolder> findChangedSyncs(SyncHolder curSH,
			Mod[] result) throws ModTypeUnsupportedException
	{
		Collection<SyncHolder> affectedSyncs = new HashSet<SyncHolder>();
		for (int i = 0; i < curSH.getVariableCount(); i++)
		{
			if (false == result[i].equals(curSH.getVariable(i).getMod()))
			{
				Mod difference = result[i].subtract(curSH.getVariable(i)
						.getMod());
				for (Variable.SyncIndexPair pair : curSH.getVariable(i))
				{
					if (pair.getSyncHolder().getSync().isModConcerned(
							pair.getIndex(), difference))
						affectedSyncs.add(pair.getSyncHolder());
				}
			}
		}
		return affectedSyncs;
	}

	private void storeModsToVariables(Collection<Variable> modifiedVariables,
			SyncHolder curSH, Mod[] result)
	{
		for (int i = 0; i < curSH.getVariableCount(); i++)
		{
			if (false == result[i].equals(curSH.getVariable(i).getMod()))
			{
				modifiedVariables.add(curSH.getVariable(i));
				curSH.getVariable(i).setMod(result[i]);
			}
		}
	}

	private void storeHighModsToVariables(
			Collection<Variable> modifiedVariables, SyncHolder curSH,
			Mod[] result)
	{
		for (int i = 0; i < curSH.getVariableCount(); i++)
		{
			if (false == result[i].equals(curSH.getVariable(i).getHighMod()))
			{
				modifiedVariables.add(curSH.getVariable(i));
				curSH.getVariable(i).setHighMod(result[i]);
			}
		}

	}

	@Override
	public String toString()
	{
		return "GraphCombinator@" + hashCode();
	}

}
